package com.cardone.generator.template;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.experimental.ExtensionMethod;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.CollectionUtils;

import com.cardone.generator.mapper.BusinessMapper;
import com.cardone.generator.mapper.EntityMapper;
import com.cardone.generator.mapper.ModuleMapper;
import com.cardone.generator.mapper.PoMapper;
import com.cardone.generator.mapper.ProjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;

/**
 * spring + spring mvc + spring jdbc
 * 
 * @author yaoht
 */
@Getter
@Setter
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
@ExtensionMethod({ org.springframework.util.Assert.class, org.apache.commons.io.FileUtils.class,
        org.springframework.beans.BeanUtils.class, com.cardone.common.template.utils.TemplateUtils.class })
public class RunSSSTemplate implements RunTemplate {
	private Configuration configuration;

	private final Map<String, String> contextMap = Maps.newHashMap();

	private String extendName = "Oracle";

	private final Map<String, Object> model = Maps.newHashMap();

	private String outputDir;

	private boolean overrideFile = true;

	private ProjectMapper projectMapper;

	private final List<String> serialVersionUIDList = Lists.newArrayList();

	@Override
	public List<PoMapper> findListPoMapper() {
		final List<PoMapper> poMapperList = Lists.newArrayList();

		if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperMap())) {
			return poMapperList;
		}

		for (final Entry<String, ModuleMapper> moduleMapperEntry : this.projectMapper.getModuleMapperMap().entrySet()) {
			if (CollectionUtils.isEmpty(moduleMapperEntry.getValue().getBusinessMap())) {
				continue;
			}

			for (final Entry<String, BusinessMapper> businessMapperEntry : moduleMapperEntry.getValue().getBusinessMap().entrySet()) {
				if (CollectionUtils.isEmpty(businessMapperEntry.getValue().getEntityMapperList())) {
					continue;
				}

				for (final EntityMapper entityMapper : businessMapperEntry.getValue().getEntityMapperList()) {
					poMapperList.add(entityMapper);
				}
			}
		}

		return poMapperList;
	}

	/**
	 * 初始化
	 */
	public void init() {
		this.projectMapper.notNull();

		this.outputDir.notNull();
	}

	private void initContext() {
		final String packageDir = StringUtils.replace(this.projectMapper.getPackageCode(), ".", File.separator);

		this.contextMap.put("outputDir", this.outputDir);

		this.contextMap.put("packageDir", packageDir);

		this.model.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

		for (final Entry<String, ModuleMapper> moduleMapperEntry : this.projectMapper.getModuleMapperMap().entrySet()) {
			moduleMapperEntry.getValue().setCode(moduleMapperEntry.getKey());

			for (final Entry<String, BusinessMapper> businessMapperEntry : moduleMapperEntry.getValue().getBusinessMap().entrySet()) {
				businessMapperEntry.getValue().setCode(businessMapperEntry.getKey());
			}
		}

		this.model.put("projectMapper", this.projectMapper);

		this.model.put("packageCode", this.projectMapper.getPackageCode());

		this.model.put("extendName", this.extendName);

		this.model.put("projectCode", this.projectMapper.getCode());

		this.contextMap.put("extendName", this.extendName);

		this.contextMap.put("projectCode", this.projectMapper.getCode());
	}

	private void makeFile(final String templateString, final String templateName, final Boolean overrideFile) throws Exception {
		RunSSSTemplate.log.info(templateString);

		RunSSSTemplate.log.info(templateName);

		final String filePathName = templateString.processString(this.contextMap);

		RunSSSTemplate.log.info(filePathName);

		final File file = new File(filePathName);

		if (file.exists()) {
			RunSSSTemplate.log.info("file.exists():true");

			if (this.overrideFile && overrideFile) {
				file.deleteQuietly();
			} else {
				return;
			}
		}

		final freemarker.template.Template template = this.configuration.getTemplate(templateName);

		this.modelPutSerialVersionUID();

		final String data = FreeMarkerTemplateUtils.processTemplateIntoString(template, this.model);

		file.writeStringToFile(data);
	}

	private void markAction() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/controller/${filename!}Controller.java";

		this.makeFile(javaTemplateString, "Controller.ftl", true);
	}

	private void markBusinessDao() throws Exception {
		String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/dao/${filename!}Dao.java";

		this.makeFile(javaTemplateString, "BusinessDao.java.ftl", false);

		javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/dao/${filename!}JdbcDao.java";

		this.makeFile(javaTemplateString, "BusinessJdbcDao.java.ftl", false);
	}

	private void markDao() throws Exception {
		String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/dao/${filename!}Dao.java";

		this.makeFile(javaTemplateString, "Dao.java.ftl", true);

		javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/dao/${filename!}JdbcDao.java";

		this.makeFile(javaTemplateString, "JdbcDao.java.ftl", true);
	}

	private void markDto() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/dto/${filename!}Dto.java";

		this.makeFile(javaTemplateString, "Dto.java.ftl", true);
	}

	private void markPo() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/po/${filename!}.java";

		this.makeFile(javaTemplateString, "Po.java.ftl", true);
	}

	private void markService() throws Exception {
		String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/service/${filename!}Service.java";

		this.makeFile(javaTemplateString, "Service.java.ftl", true);

		javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/service/${filename!}DefaultService.java";

		this.makeFile(javaTemplateString, "DefaultService.java.ftl", true);
	}

	private void markSpringConfig() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/resources/${projectCode!}/${moduleMapperKey!}/applicationContext-${filename!}.xml";

		this.makeFile(javaTemplateString, "applicationContext-service.ftl", true);
	}

	private void markSQL() throws Exception {
		String root = "${outputDir!}/src/main/resources/${projectCode!}/sql/${extendName?uncap_first}/${projectCode!}/${moduleMapperKey!}/${entityCode?uncap_first}/";

		String javaTemplateString = root + "updateByCode.ftl";

		this.makeFile(javaTemplateString, "updateByCode.ftl", true);

		javaTemplateString = root + "updateByIds.ftl";

		this.makeFile(javaTemplateString, "updateByIds.ftl", true);

		javaTemplateString = root + "findByCode.ftl";

		this.makeFile(javaTemplateString, "findByCode.ftl", true);

		javaTemplateString = root + "findByName.ftl";

		this.makeFile(javaTemplateString, "findByCode.ftl", true);

		javaTemplateString = root + "readByCode.ftl";

		this.makeFile(javaTemplateString, "readByCode.ftl", true);

		javaTemplateString = root + "readByName.ftl";

		this.makeFile(javaTemplateString, "readByCode.ftl", true);

		javaTemplateString = root + "insertByCode.ftl";

		this.makeFile(javaTemplateString, "insertByCode.ftl", true);

		javaTemplateString = root + "insertByName.ftl";

		this.makeFile(javaTemplateString, "insertByCode.ftl", true);

		javaTemplateString = root + "deleteByCode.ftl";

		this.makeFile(javaTemplateString, "deleteByCode.ftl", true);

		javaTemplateString = root + "deleteByIds.ftl";

		this.makeFile(javaTemplateString, "deleteByIds.ftl", true);

		javaTemplateString = root + "whereByCode.ftl";

		this.makeFile(javaTemplateString, "whereByCode.ftl", true);

		javaTemplateString = root + "whereByName.ftl";

		this.makeFile(javaTemplateString, "whereByCode.ftl", true);
	}

	private void markValidator() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/validator/${filename!}Validator.java";

		this.makeFile(javaTemplateString, "Validator.java.ftl", true);
	}

	private void markVo() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperKey!}/vo/${filename!}Vo.java";

		this.makeFile(javaTemplateString, "Vo.java.ftl", true);
	}

	private void modelPutSerialVersionUID() {
		String serialVersionUID = RandomStringUtils.randomNumeric(18);

		while (StringUtils.startsWithAny(serialVersionUID, new String[] { "0", "9" })
		        || this.serialVersionUIDList.contains(serialVersionUID)) {
			serialVersionUID = RandomStringUtils.randomNumeric(18);
		}

		this.serialVersionUIDList.add(serialVersionUID);

		this.model.put("serialVersionUID", serialVersionUID);
	}

	@Override
	public void run() throws Exception {
		if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperMap())) {
			return;
		}

		this.initContext();

		for (final Entry<String, ModuleMapper> moduleMapperEntry : this.projectMapper.getModuleMapperMap().entrySet()) {
			moduleMapperEntry.getValue().setCode(moduleMapperEntry.getKey());

			this.contextMap.put("moduleMapperKey", moduleMapperEntry.getKey());

			this.model.put("moduleMapperKey", moduleMapperEntry.getKey());

			this.model.put("moduleMapper", moduleMapperEntry.getValue());

			if (CollectionUtils.isEmpty(moduleMapperEntry.getValue().getBusinessMap())) {
				continue;
			}

			for (final Entry<String, BusinessMapper> businessMapperEntry : moduleMapperEntry.getValue().getBusinessMap().entrySet()) {
				if (CollectionUtils.isEmpty(businessMapperEntry.getValue().getEntityMapperList())) {
					businessMapperEntry.getValue().setEntityMapperList(new ArrayList<EntityMapper>());
				}

				for (final EntityMapper entityMapper : businessMapperEntry.getValue().getEntityMapperList()) {
					this.model.put("entityMapper", entityMapper);

					this.contextMap.put("filename", entityMapper.getCode());

					this.contextMap.put("entityCode", entityMapper.getCode());

					this.model.put("filename", entityMapper.getCode());

					this.model.put("entityCode", entityMapper.getCode());

					this.markPo();

					this.markDto();

					if (entityMapper.isMarkService() && entityMapper.isMarkController()) {
						this.markValidator();
					}

					this.markSQL();

					this.markDao();
				}

				this.setBusiness(businessMapperEntry.getValue());

				this.markVo();

				this.markBusinessDao();

				this.markService();

				this.markSpringConfig();

				this.markAction();
			}

			this.model.put("projectMapper", this.projectMapper);
		}
	}

	private void setBusiness(final BusinessMapper businessMapper) {
		this.contextMap.put("filename", businessMapper.getCode());

		this.contextMap.put("businessCode", businessMapper.getCode());

		this.model.put("businessMapper", businessMapper);

		this.model.put("businessCode", businessMapper.getCode());

		if (CollectionUtils.isEmpty(businessMapper.getEntityMapperList())) {
			businessMapper.setEntityMapperList(new ArrayList<EntityMapper>());
		}

		this.model.put("entityMapperList", businessMapper.getEntityMapperList());
	}
}
